package com.xuzhiguang.light.gateway.startup;

import com.xuzhiguang.light.gateway.common.handler.GatewayRequestHandler;
import com.xuzhiguang.light.gateway.common.handler.GatewayRequestHandlerConfig;
import com.xuzhiguang.light.gateway.common.server.Server;
import com.xuzhiguang.light.gateway.common.utils.PropertiesUtils;
import com.xuzhiguang.light.gateway.core.filter.GatewayFilter;
import com.xuzhiguang.light.gateway.core.filter.GatewayFilterConfig;
import com.xuzhiguang.light.gateway.core.filter.factory.GatewayFilterFactory;
import com.xuzhiguang.light.gateway.core.handler.BlockingQueueGatewayRequestHandler;
import com.xuzhiguang.light.gateway.core.handler.DefaultGatewayRequestHandler;
import com.xuzhiguang.light.gateway.core.handler.GatewayFilteringHandler;
import com.xuzhiguang.light.gateway.core.route.GatewayRoute;
import com.xuzhiguang.light.gateway.core.route.GatewayRouteBuilder;
import com.xuzhiguang.light.gateway.core.route.GatewayRouteConfig;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.ServiceLoader;

/**
 * @author xuzhiguang
 */
@Slf4j
public class Bootstrap {


    public static void main(String[] args) throws Throwable {

        Properties properties = GatewayConfigLoader.getInstance().load(args);

        // load gatewayConfig
        GatewayConfig gatewayConfig = PropertiesUtils.toBean(properties, GatewayConfig.class, "gateway");

        // load routes
        List<GatewayRoute> routes = new LinkedList<>();
        gatewayConfig.getRoutes().forEach(routeConfig -> routes.add(loadRoute(routeConfig)));

        // load globalFilters
        List<GatewayFilter> globalFilters = loadGlobalFilters(properties);

        // init handler
        GatewayFilteringHandler filteringHandler = new GatewayFilteringHandler(globalFilters);
        GatewayRequestHandler requestHandler;
        DefaultGatewayRequestHandler defaultGatewayRequestHandler = new DefaultGatewayRequestHandler(filteringHandler, routes);
        if (GatewayConfig.RequestHandlerType.blockingQueue.equals(gatewayConfig.getRequestHandlerType())) {
            requestHandler = new BlockingQueueGatewayRequestHandler(defaultGatewayRequestHandler);
        } else {
            requestHandler = defaultGatewayRequestHandler;
        }
        GatewayRequestHandlerConfig gatewayRequestHandlerConfig = (GatewayRequestHandlerConfig) PropertiesUtils.toBean(properties, requestHandler.getConfigClass(), requestHandler.getPrefix());
        requestHandler.init(gatewayRequestHandlerConfig);

        log.info("Loaded GatewayRequestHandler: [{}]", requestHandler.getName());

        // load servers
        List<Server> servers = loadServers(properties);

        // start server
        for (Server server : servers) {
            server.init(requestHandler);
            server.start();
        }

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            servers.forEach(Server::shutdown);
            requestHandler.shutdown();
        }));
    }

    private static List<GatewayFilter> loadGlobalFilters(Properties properties) {
        List<GatewayFilter> globalFilters = new LinkedList<>();
        ServiceLoader<GatewayFilterFactory> gatewayFilterServiceLoader = ServiceLoader.load(GatewayFilterFactory.class);
        gatewayFilterServiceLoader.forEach(gatewayFilterFactory -> {

            // config
            GatewayFilterConfig gatewayFilterConfig
                    = (GatewayFilterConfig) PropertiesUtils.toBean(properties, gatewayFilterFactory.getConfigClass(),
                    gatewayFilterFactory.getPrefix());

            GatewayFilter gatewayFilter = gatewayFilterFactory.apply(gatewayFilterConfig);
            globalFilters.add(gatewayFilter);

            log.info("Loaded GlobalFilter: [{}]", gatewayFilterFactory.getName());
        });

        return globalFilters;
    }

    private static List<Server> loadServers(Properties properties) {
        List<Server> servers = new LinkedList<>();
        ServiceLoader<Server> serviceLoader = ServiceLoader.load(Server.class);
        serviceLoader.forEach(server -> {
            Server s = PropertiesUtils.toBean(properties, server.getClass(), server.getPrefix());
            servers.add(s);

            log.info("Loaded Server: [{}]", s.getName());
        });
        return servers;
    }


    private static GatewayRoute loadRoute(GatewayRouteConfig routeConfig) {

        return new GatewayRouteBuilder().config(routeConfig).build();
    }
}
